package com.h2.ref.server.util.sec;

import java.io.IOException;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.authentication.AuthenticationServiceException;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.util.ThrowableAnalyzer;
import org.springframework.security.web.util.ThrowableCauseExtractor;

public class EntryPointFilter implements Filter {
   
   private ThrowableAnalyzer _throwableAnalyzer = new DefaultThrowableAnalyzer();
   
   private AuthenticationEntryPoint _authenticationEntryPoint;
   
   ////
   ////

   @Override
   public void init(FilterConfig arg0) throws ServletException {
      //do nothing
   }

   @Override
   public void destroy() {
      //do nothing
   }

   @Override
   public void doFilter(ServletRequest request, ServletResponse response,
         FilterChain chain) throws IOException, ServletException {
      
      try {
         chain.doFilter(request, response);
      } catch (IOException exp) {
         throw exp;
      } catch (Exception exp) {
         // Try to extract a SpringSecurityException from the stacktrace
         Throwable[] causeChain = _throwableAnalyzer.determineCauseChain(exp);
         RuntimeException ase = (AuthenticationException) _throwableAnalyzer
               .getFirstThrowableOfType(AuthenticationException.class,
                     causeChain);

         if (ase == null) {
            ase = (AccessDeniedException) _throwableAnalyzer
                  .getFirstThrowableOfType(AccessDeniedException.class,
                        causeChain);
         }

         if (ase != null) {
            handleException(request, response, chain, ase);
         } else {
            // Rethrow ServletExceptions and RuntimeExceptions as-is
            if (exp instanceof ServletException) {
               throw (ServletException) exp;
            } else if (exp instanceof RuntimeException) {
               throw (RuntimeException) exp;
            }

            // Wrap other Exceptions. These are not expected to happen
            throw new RuntimeException(exp);
         }
      }
   }

   private void handleException(ServletRequest request,
         ServletResponse response, FilterChain chain, RuntimeException exception)
         throws IOException, ServletException {
      if (exception instanceof AccessDeniedException) {
         SecurityContextHolder.getContext().setAuthentication(null);
         getAuthenticationEntryPoint().commence((HttpServletRequest) request,
               (HttpServletResponse) response,
               new AuthenticationServiceException("Access denied"));
      }
   }

   /**
    * Default implementation of <code>ThrowableAnalyzer</code> which is capable
    * of also unwrapping <code>ServletException</code>s.
    */
   private static final class DefaultThrowableAnalyzer extends
         ThrowableAnalyzer {
      /**
       * @see org.springframework.security.web.util.ThrowableAnalyzer#initExtractorMap()
       */
      protected void initExtractorMap() {
         super.initExtractorMap();

         registerExtractor(ServletException.class,
               new ThrowableCauseExtractor() {
                  public Throwable extractCause(Throwable throwable) {
                     ThrowableAnalyzer.verifyThrowableHierarchy(throwable,
                           ServletException.class);
                     return ((ServletException) throwable).getRootCause();
                  }
               });
      }
   }

   /**
    * @return the authenticationEntryPoint
    */
   public AuthenticationEntryPoint getAuthenticationEntryPoint() {
      return _authenticationEntryPoint;
   }

   /**
    * @param authenticationEntryPoint the authenticationEntryPoint to set
    */
   public void setAuthenticationEntryPoint(
         AuthenticationEntryPoint authenticationEntryPoint) {
      _authenticationEntryPoint = authenticationEntryPoint;
   }

}
